home *** CD-ROM | disk | FTP | other *** search
- #! /usr/bin/perl
- $version = "Release 7.1, September MIM";
- $AF_INET = 2; $SOCK_DGRAM = 2;
- #
- # Speak Freely Voice on Demand Server
- #
-
- $host_timeout = 30;
- $live = 0;
- $lchild = -1;
- $lwltell = -1;
- $log = 0;
- $verbose = 0;
- $hexdump = 0;
- $debug = 0;
- $port = 3456;
- $soundfile = "";
- $moptions = "";
- $program = "sfmike -a";
-
- @proto = ( "-vat ", "", "-rtp ", "" );
- @protoName = ( "VAT", "Speak_Freely", "RTP", "Gibberish" );
- @mname = ( "Jan", "Feb", "Mar", "Apr", "May", "Jun",
- "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" );
-
- $me = $0;
- if (rindex($me, "/") >= 0) {
- $me = substr($me, rindex($me, "/") + 1);
- }
-
- # Process command line arguments
-
- $arghhh = 1;
- while (@ARGV) {
- $arg = shift;
- if (substr($arg, 0, 1) eq "-" & $arghhh) {
-
- # An argument of a single dash terminates our processing
- # of arguments. Any that remain are passed to sfmike.
-
- if (length($arg) == 1) {
- $arghhh = 0;
- next;
- }
- $opt = substr($arg, 1, 1);
- $opt =~ tr/A-Z/a-z/;
- $opa = substr($arg, 2);
- if ($opt eq 'a') { # -A -- Live audio mode
- $live = 1;
- } elsif ($opt eq 'd') { # -D -- Debug output
- $debug = 1;
- } elsif ($opt eq 'l') { # -Lfile -- Log requests in file
- $log = 1;
- open(LOGFILE, ">>" . $opa);
- select(LOGFILE);
- $| = 1;
- select(stdout);
- } elsif ($opt eq 'p') { # -Pport -- Listen on given port
- $port = $opa;
- } elsif ($opt eq 'r') { # -Rprog -- Run "prog" to serve requests
- $program = $opa;
- } elsif ($opt eq 't') { # -Ttime -- Time out hosts after time seconds
- $host_timeout = $opa;
- if ($host_timeout < 20) {
- print "Timeout (-t) must be at least 20 seconds.\n";
- exit;
- }
- } elsif ($opt eq 'u' || $opt eq '?') {
- print "sfvod -- Speak Freely voice on demand server.\n";
- if (defined $version) {
- print " $version.\n";
- }
- print "Usage: sfvod [options] soundfile...\n";
- print "Options:\n";
- print " -A Send live audio\n";
- print " -Lfile Log requests in file\n";
- print " -Pport Listen on given port (default 3456)\n";
- print " -Rprog Run prog to process request (default sfmike)\n";
- print " -Ttime Time out inactive hosts after time seconds\n";
- print " -U Print this message\n";
- print " -V Show host connects and disconnects\n";
- print " -X Dump host addresses and packets in hex\n";
- print " - Pass subsequent options to sfmike\n";
- exit;
- } elsif ($opt eq "v") { # -V -- Verbose output
- $verbose = 1;
- } elsif ($opt eq "x") { # -X -- Hexadecimal dump
- $hexdump = 1;
- }
- } else {
- if (substr($arg, 0, 1) eq "-") {
- if (length($moptions) > 0) {
- $moptions .= " ";
- }
- $moptions .= $arg;
- } else {
- if (length($soundfile) > 0) {
- $soundfile .= " ";
- }
- $soundfile .= $arg;
- }
- }
- }
-
- # $AF_INET = 2; # These can vary from system to
- # $SOCK_DGRAM = 2; # system, so they're suppled by the Makefile
- $EINTR = 4; # Interrupted system call status
- $ECHILD = 10; # No children status
- $sockaddr = 'S n a4 x8';
- $protocol = getprotobyname('udp'); # We use UDP protocol
- $WNOHANG = defined &WNOHANG ? &WNOHANG : 1;
- $SIG{'CHLD'} = 'reaper'; # Register child process reaper
-
- if ($verbose) {
- print "$me: listening on port $port.\n";
- }
-
- # Create a socket to listen on the control port and bind
- # it to the port number.
-
- $sock = pack($sockaddr, $AF_INET, $port + 1, "\0\0\0\0");
- socket(S, $AF_INET, $SOCK_DGRAM, $protocol) || die "Error creating socket: $!";
- bind(S, $sock) || die "Error binding socket: $!";
- select(S);
- $| = 1;
- select(stdout);
-
- $SIG{'ALRM'} = 'tick'; # Register timeout handler
- alarm(10); # Set timeout handler
-
- # If SPEAKFREE_LWL_TELL is defined, fork a process to publish
- # our identity on the LWL server.
-
- if (defined($ENV{'SPEAKFREE_LWL_TELL'})) {
- if (($lwltell = fork()) == 0) {
- $SIG{'INT'} = 'killed';
- $zexec = "sfspeaker -w$port";
- if ($debug) {
- print("Exec: $zexec\n");
- }
- exec($zexec);
- exit;
- }
- }
-
- $con = 1;
- while (1) {
-
- # Wait until a packet arrives from the control port.
-
- # You might be wondering why we're doing a select()
- # here when we're only interested in waiting on a
- # single file discriptor. Well, the reason is that
- # there's a stone bug in Perl 5.004 which causes the
- # first recv() after a signal was processed (hence using
- # the "restartable system call" mechanism) to return
- # the null string as the sender's address, notwithstanding
- # the fact that the data for the packet has been correcly
- # stored into the string argument.
- #
- # If one uses select(), however, to block until a
- # packet is ready to recv(), the problem does not
- # occur. So that's the way we'll do it.
-
- $rin = '';
- vec($rin, fileno(S), 1) = 1;
- $nfound = select($rout = $rin, undef, undef, undef);
-
- if ($nfound == 0) {
- # &tick();
- next;
- }
-
- $addr = recv(S, $sockread, 512, 0);
- if (!defined($addr)) {
- if ($debug) {
- print("Recv error: $!\n");
- }
- if ($! == $EINTR || $! == $ECHILD) {
- if ($debug) {
- print(" ...ignoring\n");
- }
- next;
- }
- die "Error receiving from socket: $!";
- }
- if ($hexdump) {
- printf("Address, length %d:\n", length($addr));
- &hexdump($addr, ' ');
- }
- if (length($addr) < 16) {
- if ($debug) {
- print("Recv: Void address\n");
- }
- next;
- }
- if ($hexdump) {
- printf("Packet, length %d:\n", length($sockread));
- &hexdump($sockread, ' ');
- }
- $pr = (ord($sockread) >> 6) & 3; # Extract protocol from first byte
- ($af, $rport, $inetaddr) = unpack($sockaddr, $addr);
- @inetaddr = unpack('C4', $inetaddr);
- # Build dotted IP address to pass to sfmike
- $IPaddress = "$inetaddr[0].$inetaddr[1].$inetaddr[2].$inetaddr[3]";
-
- if (defined $hosts{$IPaddress}) {
-
- # Check for a BYE packet
-
- $isbye = 0;
- if ($pr == 0) {
- if (ord(substr($sockread, 1, 1)) == 2) {
- $isbye = 1;
- }
- } else {
- $isbye = &isRTCPbye;
- }
- if ($isbye) {
- if ($debug) {
- print "BYE received from $IPaddress\n";
- }
-
- # If child process still active, kill it. This allows
- # the user to end the transmission at any time by
- # disconnecting.
-
- if (!$live && ($timer{$hosts{$IPaddress}} == 0)) {
- if ($debug) {
- printf "Killing process $hosts{$IPaddress}\n";
- }
- kill('INT', $hosts{$IPaddress});
- }
- &closeout($IPaddress);
- &updlive();
- if ($verbose) {
- print "$me: $IPaddress bye.\n";
- }
- next;
- }
-
- # If we're in the process of timing out this connection,
- # reset the timer every time we receive a new packet.
- # This keeps us from timing out the host and inadvertently
- # restarting the transmission.
-
- if ($timer{$hosts{$IPaddress}} != 0) {
- $timer{$hosts{$IPaddress}} = time();
- }
- next;
- }
-
- # Only look up the host name if we're in verbose mode or
- # writing a log file. Host lookups can take a while and
- # there's no need to create the extra network traffic unless
- # we really need the host name.
-
- if ($log || $verbose) {
- $name = "";
- ($name, $aliases, $length, @addrs) = gethostbyaddr($inetaddr,
- length($inetaddr));
- if (length($name) == 0) {
- $name = $IPaddress;
- }
- if ($verbose) {
- print "$me: $name ($IPaddress) $protoName[$pr] connect.\n";
- }
-
- # Write a log file entry in a format strongly resembling
- # NCSA Common HTTPD log file format. We always use GMT
- # and zero for the length of the transmission. Suitable
- # ugly hacks could remove these limitations. In place
- # of "HTTP" we show the protocol we used for the transmission.
-
- if ($log) {
- ($ss, $mm, $hh, $mday, $mon, $yy, $wd, $yd, $isdst) =
- gmtime(time());
- print LOGFILE
- sprintf("%s - - [%02d/%s/%d:%02d:%02d:%02d +0000] \"GET %s %s/1.0\" 200 0\n",
- $name,
- $mday, $mname[$mon], $yy + 1900, $hh, $mm, $ss,
- $soundfile, $protoName[$pr]);
- }
- }
-
- # Now we're actually ready to do something. Fork a child
- # process and invoke sfspeaker (or whatever program the user
- # specified with the "-r" option) to play whatever was
- # specified on our command line. Note that we include
- # the protocol of the request we received on the command
- # line in order to respond in the same protocol as that
- # of the request.
-
- if (!$live && (($child = fork()) == 0)) {
- $SIG{'INT'} = 'killed';
- $zexec = "$program $proto[$pr] $moptions -p$IPaddress/$port $soundfile";
- if ($debug) {
- print("Exec: $zexec\n");
- }
- exec($zexec);
- exit;
- }
- $con++;
-
- # Save information about the request in progress:
- #
- # $children{$child_process_pid} = IP address of host
- #
- # $timer{$child_process_pid} = 0 while transmission is
- # underway. When the child process
- # exits, this is set to the time
- # the process exited, and is updated
- # every time we get another ID
- # packet from the host. This is
- # used by the timer to timeout
- # hosts that go away without sending
- # a BYE.
- #
- # $hosts{$IPaddress} = Child process serving the request
- # from that IP address.
-
- $children{$child} = $IPaddress;
- $timer{$child} = 0;
- $hosts{$IPaddress} = $child;
- &updlive;
- }
-
- # &closeout(ip) -- Close out host with given IP address
-
- sub closeout {
- local($h) = $_[0];
- local($ch) = $hosts{$h};
- delete $children{$ch};
- delete $timer{$ch};
- delete $hosts{$h};
- }
-
- # &dumpstat -- Dump state arrays
-
- sub dumpstat {
- print "Children:\n"; foreach $s (keys(%children)) { print " $s $children{$s}\n"; }
- print "Hosts:\n"; foreach $s (keys(%hosts)) { print " $s $hosts{$s}\n"; }
- print "Timer:\n"; foreach $s (keys(%timer)) { print " $s $timer{$s}\n"; }
- }
-
- # &killed -- Catch interrupt when user disconnects before
- # we're done playing the sound.
-
- sub killed {
- exit;
- }
-
- # &reaper -- Catch terminating child processes and start
- # the inactivity timeout running.
-
- sub reaper {
- local($pid);
-
- if ($debug) {
- print "Reaper...\n";
- }
- while (1) {
- $pid = waitpid(-1, $WNOHANG);
- if ($debug) {
- print " Reaped process $pid\n";
- }
- last if ($pid < 1);
- if ($live && $pid == $lchild) {
- $lchild = -1;
- &updlive();
- } elsif (defined $timer{$pid}) {
- $timer{$pid} = time();
- }
- }
- if ($debug) {
- print "Reaped.\n";
- }
- $SIG{'CHLD'} = 'reaper'; # Reset child process reaper
- }
-
- # &tick -- Scan the list of open connections and check for any
- # which haven't sent an identity packet in $host_timeout
- # seconds. If that's the case, terminate the connection
- # (rendering it eligible for re-connection if and when we
- # see another packet from this host).
-
- sub tick {
- local($t, $h, $l);
-
- if ($debug) {
- print("Tick...\n");
- }
- $t = time();
- foreach $h (keys(%children)) {
- if ($timer{$h} != 0) {
- $l = time() - $timer{$h};
- if ($l > $host_timeout) {
- &closeout($children{$h});
- &updlive();
- if ($verbose) {
- print "$me: $IPaddress timeout.\n";
- }
- }
- }
- }
- alarm(10);
- $SIG{'ALRM'} = 'tick'; # Reset timeout handler
- }
-
- # &isRTCPbye -- See if a received packet is an RTCP BYE
-
- sub isRTCPbye {
- local($p0, $p1, $len, $n, $end, $sawbye);
-
- $sawbye = 0;
- $len = length($sockread);
- $p0 = ord($sockread);
- $p1 = ord(substr($sockread, 1, 1));
- if ((($p0 >> 6) == 2 || ($p0 >> 6) == 1) &&
- (($p0 & 0x20) == 0) &&
- (($p1 == 200) || ($p1 == 201))) {
- }
-
- $n = 0;
- do {
- if (ord(substr($sockread, $n + 1, 1)) == 203) {
- $sawbye = 1;
- }
- $n += (((ord(substr($sockread, $n + 2, 1)) * 256) +
- ord(substr($sockread, $n + 3, 1))) + 1) * 4;
- } while (($n < $len) && ((ord(substr($sockread, $n, 1)) >> 6) == 2));
- $n == $len && $sawbye;
- }
-
- # &updlive -- Update list of active live audio destinations
-
- sub updlive {
- local($a, $b, $zexec);
-
- if ($live) {
- if ($lchild >= 0) {
- kill('INT', $lchild);
- } else {
- $a = "";
- foreach $b (keys(%hosts)) {
- if (length($a) > 0) {
- $a .= " ";
- }
- $a .= "-p$b/$port";
- }
- if (length($a) > 0) {
- if (verbose) {
- print "$me: sending to $a.\n";
- }
- if (($lchild = fork()) == 0) {
- $SIG{'INT'} = 'lkilled';
- $zexec = "$program $moptions $a";
- if ($debug) {
- print("Exec: $zexec\n");
- }
- exec($zexec);
- exit;
- }
- } else {
- if (verbose) {
- print "$me: idle.\n";
- }
- }
- }
- }
- }
-
- # &lkilled -- Catch interrupt when live audio player terminates
-
- sub lkilled {
- exit;
- }
-
- # &hexdump -- Dump contents of string in hexadecimal
-
- sub hexdump {
- local($d, $xdp) = @_;
- local($adr) = 0;
- local($l) = 0;
-
- while (length($d) > 0) {
- if ($l == 0) {
- printf("%s%04X: ", $xdp, $adr);
- }
- if ($l == 8) {
- printf(" :");
- }
- printf(" %02X", unpack('C', $d));
- $d = substr($d, 1);
- $adr++;
- $l = ($l + 1) % 16;
- if ($l == 0) {
- print("\n");
- }
- }
- if ($l > 0) {
- print("\n");
- }
- }
-